using System;
using System.Collections;
using System.Reflection;
using System.Text;
using System.Timers;
using BussinesLayerDataTier;
using DataLayer;
using DataLayerUtilities;

namespace BussinesLayerLogicTier
{
	/// <summary>
	/// Summary description for ObjectManager.
	/// </summary>
	public class ObjectManager
	{
		private static Hashtable pool = new Hashtable();
		private static Timer timer = new Timer(30000);
		
		static ObjectManager()
		{
			timer.Start();
			timer.Elapsed+=new ElapsedEventHandler(OnGarbageCollector);
		}
		
		private static object FindByPK(object[] primaryKeysValues, Type type)
		{
			StringBuilder pks = new StringBuilder();
			foreach (object o in primaryKeysValues)
			{
				pks.Append(o.ToString());
			}
			
			string hashCode = EncryptionServices.GetMd5Sum(pks.ToString() + type.Name);
			
			object obj = null;
			if (pool.ContainsKey(hashCode))
				obj = ((PoolObject) pool[hashCode]).Tag;
			
			return obj;
		}
		
		private static void UpdatePool(object[] objs)
		{
			foreach (object obj in objs)
			{
				StringBuilder hashCode = new StringBuilder();
				
				int ttl = 120;
				
				if (obj.GetType().GetCustomAttributes(typeof(DataObjectAttribute),false).Length > 0)
					ttl = (obj.GetType().GetCustomAttributes(typeof (DataObjectAttribute), false)[0] as DataObjectAttribute).TTL;
					
				foreach (PropertyInfo pi in obj.GetType().GetProperties())
				{
					if (pi.GetCustomAttributes(typeof (FieldAttribute), false).Length > 0)
					{
						FieldAttribute fa = pi.GetCustomAttributes(typeof (FieldAttribute), false)[0] as FieldAttribute;
						if (fa.PrimaryKey)
						{
							hashCode.Append(pi.GetValue(obj, null).ToString());
						}
					}
				}
				
				string md5sum = EncryptionServices.GetMd5Sum(hashCode.ToString() + obj.GetType().Name);
				
				if (! pool.ContainsKey(md5sum))
				{
					PoolObject po = new PoolObject(obj,ttl);
					
					pool.Add(md5sum,po);
				}
			}
		}
		
		
		public static object[] Load(Query query)
		{
			object[] objs = DataObjectManager.LoadObject(query);

			UpdatePool(objs);

			return objs;
		}

		
		public static object Load(DataObject obj)
		{
			if ( obj == null )
				return null;
			if ( obj.LoadKeys.Count == 0)
				throw new ApplicationException("Couldn't load the PrimaryKeys from LoadKeys");
			
			ArrayList pks = new ArrayList();
			foreach (DictionaryEntry en in obj.LoadKeys)
			{
				pks.Add(en.Value);
			}
			
			object[] objs = Load(pks.ToArray(), obj.GetType(), obj.ReloadType);
			if (objs.Length>0)
				obj = objs[0] as DataObject;
			else
				obj = null;
			return obj;
		}
		
		/// <summary>
		/// Carga una coleccion de DataObjects del pool o la base de datos
		/// </summary>
		/// <param name="container"></param>
		/// <param name="myCollection"></param>
		/// <param name="foreingKeyValue"></param>
		/// <returns></returns>
		public static object Load(DataObject container, DataObjectCollection myCollection, object foreingKeyValue)
		{
			Type type = container.GetType();
			CollectionAttribute myCa = null;
			PropertyInfo myPi = null;
			
			// Get the proper CollectionAttribute from container's type
			foreach (PropertyInfo pi in type.GetProperties())
				if (pi.PropertyType == myCollection.GetType())
				{
					try
					{
						myPi = pi;
						myCa = myPi.GetCustomAttributes(typeof (CollectionAttribute), false)[0] as CollectionAttribute;
					}
					catch (Exception e)
					{
						throw new ApplicationException("Couldn't read CollectionAttribute from: " + type.Name + "." + pi.Name, e);
					}
					break;
				}


			if ( myPi != null )
			{
				TableAttribute ta;
				Type objType = myCa.CollectionType;
				
				try
				{
					ta = objType.GetCustomAttributes(typeof (TableAttribute), false)[0] as TableAttribute;
				}
				catch ( Exception e)
				{
					throw new ApplicationException("Couldn't read TableAttribute from class " + myCa.CollectionType.Name, e);
				}
				
//				// Obtain numer of object that must exist in the collection
//				Command cmd = DataObjectManager.DataSource.CreateCommand("select count(*) from " + ta.Name + " where " + 
//				                                                         myCa.ForeignKey + " = @value");
//				cmd.AddParameter("@value", foreingKeyValue);
//				int objectsMustExist = (int) cmd.ExecuteScalar();
//					
				if ( myCollection.ReloadType == ReloadType.ObjectData )
				{
					// Reload from DataLayer
					string query = "select * from " + ta.Name + " where " + myCa.ForeignKey + " = @value";
					object[] parms = new object[] {foreingKeyValue};
					
					Query q = new Query(objType, query, parms, ta.Name);
					object[] objs = DataObjectManager.LoadObject(q);
					
					foreach ( DataObject obj in objs )
						myCollection.Add(obj);
					
					myCollection.ReloadType = ReloadType.None;
				} 
				else if ( myCollection.ReloadType == ReloadType.ObjectPool )
				{
					// Reload from pool
					object[] objectsInPool = FindObjectsPool(objType, myPi, foreingKeyValue);
					
					foreach ( DataObject obj in objectsInPool as DataObject[] )
						myCollection.Add(obj);
					
					myCollection.ReloadType = ReloadType.None;
					
				} else if ( myCollection.ReloadType == ReloadType.None ) 
					return myCollection;
				
				
			} else
				throw new ApplicationException("The " + type.Name + " class doesn't have a property of type " +
				                               myCollection.GetType().Name);
			
			return myCollection;
			
		}

		/// <summary>
		/// Localizado los objectos del pool que sean del tipo especificado y concuerde su propiedad con el valor deseado
		/// </summary>
		/// <param name="type">Type del objecto que se quire consultar</param>
		/// <param name="pi">Propiedad que se va a comparar</param>
		/// <param name="value">Valor que debe coincidir</param>
		/// <returns>Objectos localizado en el pool</returns>
		private static object[] FindObjectsPool(Type type, PropertyInfo pi, object value)
		{
			ArrayList tmpArray = new ArrayList();
			foreach ( PoolObject poolObj in pool )
			{
				Type pooledObjType = poolObj.Tag.GetType();
				if ( pooledObjType == type )
					foreach ( PropertyInfo piPooled in pooledObjType.GetProperties())
						if ( piPooled.Name == pi.Name && pi.GetValue(poolObj.Tag, null) == value )
						{
							// Pooled Object's Property's Value match the desired value
							tmpArray.Add(poolObj.Tag);
						}
			}
			return tmpArray.ToArray();
		}

		public static object[] Load(object[] primaryKeysValues, Type objectType, ReloadType reloadType)
		{
			object obj = null;
			
			if (reloadType == ReloadType.None)
				obj = FindByPK(primaryKeysValues, objectType);

			if (obj!=null && reloadType != ReloadType.ObjectPool)
			{
				object[] objs = {obj};
				return objs;
			}
			else
			{
				object[] objs = DataObjectManager.LoadObject(primaryKeysValues, objectType, reloadType);
				
				UpdatePool(objs);
				
				return objs;
			}
		}
		
		public static object[] Load(object[] primaryKeysValues, Type objectType)
		{
			return Load(primaryKeysValues, objectType, ReloadType.None);
		}

		
		public static object[] Load(object[][] primaryKeysValues, Type objectType, ReloadType reloadType)
		{
			ArrayList objs = new ArrayList();
			object[] obj;
			
			foreach (object[] pks in primaryKeysValues)
			{
				obj = Load(pks, objectType, reloadType);
				foreach (object o in obj)
					objs.Add(o);
			}
			
			return objs.ToArray();
		}
		
		public static object[] Load(object[][] primaryKeysValues, Type objectType)
		{
			return Load(primaryKeysValues, objectType, ReloadType.None);
		}
		
		
		public static void Save(object obj)
		{
			DataObjectManager.SaveObject(obj);
		}
		
		public static void Save(object[] objs)
		{
			DataObjectManager.SaveObject(objs);
		}
		
		public static void Delete(object obj)
		{
			DataObjectManager.DeleteObject(obj);
		}
		
		private static void OnGarbageCollector(object sender, ElapsedEventArgs e)
		{
			PoolObject po;
			Hashtable poolTmp = (Hashtable)pool.Clone();
			foreach (DictionaryEntry en in poolTmp)
			{
				po = (PoolObject) en.Value;
				if ( DateTime.Now.ToOADate() >= po.CreationTime.AddSeconds(po.TTL).ToOADate() )
					pool.Remove(en.Key);
			}
		}
	}
	
}
